from channels.generic.websocket import WebsocketConsumer
from channels.exceptions import StopConsumer
import json
import threading
from asgiref.sync import async_to_sync
from web import models
import time

def create_node(task_object, task_id):
    """
    创建节点
    :return:
    """
    # 动态创建节点信息，并返回给前端
    # 第一步：去数据库创建所有节点
    # 添加节点对象到列表中

    # 在数据库生成节点
    # 判断当前任务是否已经创建过图表数据
    node_object_list = models.Node.objects.filter(task_id=task_id)

    # 有数据直接使用数据库的，为了创建后的不再创建（初始化一次就够）
    if node_object_list:
        return node_object_list

    if not node_object_list:  # 数据库没有节点，开始进行创建
        node_object_list = []
        # 1.先创建节点
        start_node = models.Node.objects.create(text='开始', task_id=task_id)
        node_object_list.append(start_node)

        # 判断第一个钩子是否自定义脚本：任务单对象 before_download_script
        if task_object.before_download_script:
            start_node = models.Node.objects.create(text="下载前", task_id=task_id, parent=start_node)
            node_object_list.append(start_node)

        # 下载节点
        down_load_node = models.Node.objects.create(text='下载', task_id=task_id, parent=start_node)
        node_object_list.append(down_load_node)

        if task_object.after_download_script:
            down_load_node = models.Node.objects.create(text="下载后", task_id=task_id, parent=down_load_node)
            node_object_list.append(down_load_node)

        # 上传节点
        upload_node = models.Node.objects.create(text='上传', task_id=task_id, parent=down_load_node)
        node_object_list.append(upload_node)

        # 服务器节点需要考虑服务器的个数
        # task_object = models.DeployTask.objects.filter(pk=task_id).first()
        # 跨表查询获得所有的服务器对象
        for server_object in task_object.project.servers.all():
            server_node = models.Node.objects.create(text=server_object.hostname,
                                                     task_id=task_id,
                                                     parent=upload_node,
                                                     servers=server_object
                                                     )
            node_object_list.append(server_node)

            # 判断发布前钩子
            if task_object.before_deploy_script:
                server_node = models.Node.objects.create(text='发布前',
                                                         task_id=task_id,
                                                         parent=server_node,
                                                         servers=server_object
                                                         )
                node_object_list.append(server_node)

            # 额外的再添加一个节点 : 发布节点
            deploy_node = models.Node.objects.create(text='发布',
                                                     task_id=task_id,
                                                     parent=server_node,
                                                     servers=server_object
                                                     )
            node_object_list.append(deploy_node)

            # 同理 发布后的钩子
            if task_object.after_deploy_script:
                after_deploy_node = models.Node.objects.create(text='发布后',
                                                               task_id=task_id,
                                                               parent=deploy_node,
                                                               servers=server_object
                                                               )
                node_object_list.append(after_deploy_node)

    return node_object_list

def convert_object_to_gojs(node_object_list):
    """
    将对象列表转换为gojs识别的json格式
    :param node_object_list:
    :return:
    """
    node_list = []
    for node_object in node_object_list:
        temp = {
            'key': str(node_object.pk),
            'text': node_object.text,
            'color': node_object.status
        }
        # 判断当前节点对象是否有父节点,如果有则需要再添加一对键值对 parent
        if node_object.parent:
            temp['parent'] = str(node_object.parent_id)
        # 添加到列表中
        node_list.append(temp)  # [{}, {}, {}]

    return node_list

class PublishConsumer(WebsocketConsumer):

    def deploy(self, task_object, task_id):
        # 第一步：开始，找到数据库中的开始节点，给他变状态（颜色），同时将状态给前端返回。
        start_node = models.Node.objects.filter(text='开始', task_id=task_id).first()
        start_node.status = "green"
        start_node.save()
        # 传给前端deploy.html的initWebSocket的onmessage里
        async_to_sync(self.channel_layer.group_send)(
            task_id, {'type': 'my.send', 'message': {'code': 'update', 'node_id': start_node.id, 'color': "green"}}
        )



        import os
        from django.conf import settings
        project_name = task_object.project.title
        uid = task_object.uid
        script_folder = os.path.join(settings.DEPLOY_CODE_PATH, project_name, uid, "scripts")
        project_folder = os.path.join(settings.DEPLOY_CODE_PATH, project_name, uid, project_name)
        if not os.path.exists(script_folder):
            os.makedirs(script_folder)
        if not os.path.exists(project_folder):
            os.makedirs(project_folder)

        # 第二步：下载前
        if task_object.before_download_script:
            # 具体动作执行钩子脚本，执行成功才变绿色。失败变红色
            # TODO 要去做一些具体的动作，执行钩子脚本
            # task_object.before_download_script = "print(123)"  在部署服务器上执行
            # 在发布机上执行钩子脚本的内容
            #   1.将钩子的内容写入到本地脚本文件中
            #   2.在本地执行这个脚本文件，如果成功 green，否则 red，
            status = "green"
            try:
                script_name = "before_download_script.py"
                script_path = os.path.join(script_folder, script_name)
                with open(script_path, mode='w', encoding='utf-8') as f:
                    f.write(task_object.before_download_script)

                import subprocess
                # 本地执行  # shell=True代表可以有空格  # cwd就是cd到目录
                subprocess.check_output("python {0}".format(script_name), shell=True, cwd=script_folder)
            except Exception as e:
                status = "red"

            before_download_node = models.Node.objects.filter(text='下载前', task_id=task_id).first()
            before_download_node.status = "green"
            before_download_node.save()
            # 传给前端deploy.html的initWebSocket的onmessage里
            async_to_sync(self.channel_layer.group_send)(
                task_id,
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': before_download_node.id, 'color': status}}
            )

            if status == 'red':
                return


        # 第三步：下载
        # TODO 要去做一些具体的动作，去git拉取
        # 1.获取仓库地址：task_object.project.repo
        # 2.去仓库中下载：git clone -b v1 https://gitee.com/kolorbgg/demo.git
        status = "green"
        try:
            from utils.repo import GitRepository
            # GitRepository("代码下载到路径", "下载源", "版本")
            GitRepository(project_folder, task_object.project.repo, task_object.tag)
        except Exception as e:
            status = "red"

        if status == 'red':
            return



        time.sleep(2)
        download_node = models.Node.objects.filter(text='下载', task_id=task_id).first()
        download_node.status = "green"
        download_node.save()
        # 传给前端deploy.html的initWebSocket的onmessage里
        async_to_sync(self.channel_layer.group_send)(
            task_id,
            {'type': 'my.send', 'message': {'code': 'update', 'node_id': download_node.id, 'color': "green"}}
        )

        # 第四步：下载后
        if task_object.after_download_script:
            # TODO 要去做一些具体的动作，去git拉取
            after_download_node = models.Node.objects.filter(text='下载后', task_id=task_id).first()
            after_download_node.status = "green"
            after_download_node.save()
            # 传给前端deploy.html的initWebSocket的onmessage里
            async_to_sync(self.channel_layer.group_send)(
                task_id,
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': after_download_node.id, 'color': "green"}}
            )

        # 第五步：上传
        # 调用paramiko上传，如果成功，服务器颜色变化。
        upload_node = models.Node.objects.filter(text='上传', task_id=task_id).first()
        upload_node.status = "green"
        upload_node.save()
        # 传给前端deploy.html的initWebSocket的onmessage里
        async_to_sync(self.channel_layer.group_send)(
            task_id,
            {'type': 'my.send', 'message': {'code': 'update', 'node_id': upload_node.id, 'color': "green"}}
        )

        # 第六步：连接每台服务器
        # 循环此任务的所有机器
        for server_object in task_object.project.servers.all():
            # 6.1步，上传代码
            # 通过paramiko将代码上传到服务器
            time.sleep(2)
            server_node = models.Node.objects.filter(text=server_object.hostname, task_id=task_id,
                                                     servers=server_object).first()
            server_node.status = "green"
            server_node.save()
            # 传给前端deploy.html的initWebSocket的onmessage里
            async_to_sync(self.channel_layer.group_send)(
                task_id,
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': server_node.id, 'color': "green"}}
            )

            # 6.2步：发布前钩子
            if task_object.before_deploy_script:
                # TODO 要去做一些具体的动作
                before_deploy_node = models.Node.objects.filter(text='发布前', task_id=task_id,
                                                                servers=server_object).first()
                before_deploy_node.status = "green"
                before_deploy_node.save()
                # 传给前端deploy.html的initWebSocket的onmessage里
                async_to_sync(self.channel_layer.group_send)(
                    task_id,
                    {'type': 'my.send',
                     'message': {'code': 'update', 'node_id': before_deploy_node.id, 'color': "green"}}
                )
            # 6.3步：处理发布
            # TODO 要去做一些具体的动作
            deploy_node = models.Node.objects.filter(text='发布', task_id=task_id,
                                                     servers=server_object).first()
            deploy_node.status = "green"
            deploy_node.save()
            # 传给前端deploy.html的initWebSocket的onmessage里
            async_to_sync(self.channel_layer.group_send)(
                task_id,
                {'type': 'my.send', 'message': {'code': 'update', 'node_id': deploy_node.id, 'color': "green"}}
            )
            # 6.4步：发布后
            if task_object.after_deploy_script:
                # TODO 要去做一些具体的动作，去git拉取
                after_deploy_node = models.Node.objects.filter(text='发布后', task_id=task_id,
                                                               servers=server_object).first()
                after_deploy_node.status = "green"
                after_deploy_node.save()
                # 传给前端deploy.html的initWebSocket的onmessage里
                async_to_sync(self.channel_layer.group_send)(
                    task_id,
                    {'type': 'my.send',
                     'message': {'code': 'update', 'node_id': after_deploy_node.id, 'color': "green"}}
                )

    def websocket_connect(self, message):
        """ 客户端发来连接请求之后就会被触发 """

        """ 服务端接收连接，向客户端浏览器发送一个加密字符串 """
        task_id = self.scope['url_route']['kwargs'].get('task_id')
        self.accept()
        # 将当前的用户加入到群聊中 (括号内子一个参数是群号,第二个是用户的唯一表示)
        async_to_sync(self.channel_layer.group_add)(task_id, self.channel_name)

        # 当用户打开页面时，如果已经创建好节点了，则默认展示所有节点数据。
        node_queryset = models.Node.objects.filter(task_id=task_id)
        # 数据库有节点，应该给用户返回
        # 优化: 查询当前任务是否已经初始化节点数据,如果有就直接返回给前端展示
        if node_queryset:
            node_list = convert_object_to_gojs(node_queryset)
            # 发送数据给前端  (单发,谁刷新页面给谁发送)
            self.send(text_data=json.dumps({"code":'init','data':node_list}))

    def websocket_receive(self, message):
        """ 客户端浏览器向服务端发送消息，此方法自动触发 """
        # 获取用户发送过来的指令：init
        txt = message['text']
        """ 服务单给客户端回一条消息"""
        task_id = self.scope['url_route']['kwargs'].get('task_id')
        task_object = models.DeployTask.objects.filter(id=task_id).first()

        # 获取用户发送过来的指令：init
        if txt == 'init':
            """node_list = [
                {"key": "start", "text": '开始', "figure": 'Ellipse'},
                {"key": "download", "parent": 'start', "text": '下载代码'},
                {"key": "compile", "parent": 'download', "text": '本地编译'},

            ]"""

            # 第一步：没有创建过，则去数据库创建所有节点。有的话，直接读取
            node_object_list = create_node(task_object, task_id)

            # 第二步：根据对象列表生成特定JSON格式数据给用户返回
            # 2.构造gojs需要的节点数据
            node_list = convert_object_to_gojs(node_object_list)

            # 单独给当前连接对象发送数据
            # self.send(text_data=json.dumps({"code":'init','data':node_list}))

            # 第三步：把数据通过websocket发给前端，前端赋值给gojs
            # 给特定群号里面的用户发送数据
            async_to_sync(self.channel_layer.group_send)(task_id, {'type': 'my.send',
                                                                   'message': {'code': 'init', 'data': node_list}})

        # 获取用户发送过来的指令：deploy
        if txt == 'deploy':
            # 代码发布
            # self.deploy(task_object, task_id)
            thread = threading.Thread(target=self.deploy,args=(task_object, task_id,))
            thread.start()

    def my_send(self, event):
        """发送数据功能
        async_to_sync 会循环调用该方法,给群聊这里面的每一个用户发送数据
        for obj in [obj1,obj2,obj3]:
            obj.send()
        """
        # 获取message后面的数据
        # print(123)
        message = event.get('message')
        # 直接发送
        self.send(json.dumps(message))

    def websocket_disconnect(self, message):
        # 当前用户断开连接之后,应该提出群聊
        task_id = self.scope['url_route']['kwargs'].get('task_id')
        # 去群里将用户剔除
        async_to_sync(self.channel_layer.group_discard)(task_id, self.channel_name)
        raise StopConsumer()
